using System;
using System.Collections.Generic;
#if(!WindowsCE)
using System.Linq.Expressions;
#endif
namespace ValidationFramework.Reflection
{
    /// <summary>
    /// Provides an in memory cache of <see cref="TypeDescriptor"/>s.
    /// </summary>
    /// <example>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\AddWithTypeCacheExample.cs" title="The following example shows how to programmatically add validation Rules to a property." lang="cs" region="Example"/>
    /// <code source="Examples\ExampleLibraryVB\Reflection\AddWithTypeCacheExample.vb" title="The following example shows how to programmatically add validation Rules to a property." lang="vbnet" region="Example"/>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\TypeCacheExample.cs" title="The following example shows how to programmatically add validation Rules to a property." lang="cs" region="Example"/>
    /// <code source="Examples\ExampleLibraryVB\Reflection\TypeCacheExample.vb" title="The following example shows how to programmatically add validation Rules to a property." lang="vbnet" region="Example"/>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\AddCustomRuleWithTypeCacheExample.cs" lang="cs" title="This example shows how to programmatically add a CustomRule to a property." region="Example"/>
    /// <code source="Examples\ExampleLibraryCSharp\Reflection\AddCustomRuleWithTypeCacheStrongTypedExample.cs" lang="cs" title="This example shows how to programmatically add a CustomRule to a property." region="Example"/>
    /// <code source="Examples\ExampleLibraryVB\Reflection\AddCustomRuleWithTypeCacheExample.vb" lang="vbnet" title="This example shows how to programmatically add a CustomRule to a property." region="Example"/>
    /// </example>
	public static class TypeCache
    {
        // --- Fields

        private static readonly Dictionary<RuntimeTypeHandle, TypeDescriptor> typeDescriptorDictionary = new Dictionary<RuntimeTypeHandle, TypeDescriptor>();
        private static readonly object typeDescriptorDictionaryLock = new object();


        // --- Methods

        /// <summary>
        /// Clear all <see cref="TypeDescriptor"/>s from <see cref="TypeCache"/>.
        /// </summary>
        public static void Clear()
        {
            typeDescriptorDictionary.Clear();
        }

        /// <summary>
        /// Get, and adds to the cache, a <see cref="TypeDescriptor"/> for a <see cref="Type"/>. 
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> for which to get the <see cref="TypeDescriptor"/>.</typeparam>
        /// <returns>A <see cref="TypeDescriptor"/> corresponding  to <typeparamref name="T"/>.</returns>
        public static TypeDescriptor GetType<T>() where T: class
        {
            return GetType(typeof(T).TypeHandle);
        }

        /// <summary>
        /// Get, and adds to the cache, a <see cref="TypeDescriptor"/> for a <see cref="RuntimeTypeHandle"/>. 
        /// </summary>
        /// <param name="runtimeTypeHandle">The <see cref="RuntimeTypeHandle"/> for which to get the <see cref="TypeDescriptor"/>.</param>
        /// <returns>A <see cref="TypeDescriptor"/> corresponding  to <paramref name="runtimeTypeHandle"/>.</returns>
        /// <exception cref="ArgumentException"><paramref name="runtimeTypeHandle"/> represents an interface.</exception>
        public static TypeDescriptor GetType(RuntimeTypeHandle runtimeTypeHandle)
        {
            //no need to check for null as RuntimeTypeHandle is a struct
            TypeDescriptor typeDescriptor;

            if (!typeDescriptorDictionary.TryGetValue(runtimeTypeHandle, out typeDescriptor))
            {
                lock (typeDescriptorDictionaryLock)
                {
                    if (!typeDescriptorDictionary.TryGetValue(runtimeTypeHandle, out typeDescriptor))
                    {
                        typeDescriptor = new TypeDescriptor(runtimeTypeHandle);
                        typeDescriptorDictionary.Add(runtimeTypeHandle, typeDescriptor);
                    }
                }
            }
            return typeDescriptor;
        }

		/// <summary>
		/// Removes <see cref="TypeDescriptor"/> for <see cref="Type"/> from cache. 
		/// </summary>
		/// <typeparam name="T">The <see cref="Type"/> for which to get the <see cref="TypeDescriptor"/>.</typeparam>
		public static void RemoveType<T>() where T : class
		{
			RemoveType(typeof(T).TypeHandle);
		}

		/// <summary>
		/// Removes <see cref="TypeDescriptor"/> for <see cref="Type"/> from cache. 
		/// </summary>
		/// <param name="runtimeTypeHandle">The <see cref="RuntimeTypeHandle"/> for which to get the <see cref="TypeDescriptor"/>.</param>
		/// <exception cref="ArgumentException"><paramref name="runtimeTypeHandle"/> represents an interface.</exception>
		public static void RemoveType(RuntimeTypeHandle runtimeTypeHandle)
		{
			//no need to check for null as RuntimeTypeHandle is a struct
			TypeDescriptor typeDescriptor;

			if (typeDescriptorDictionary.TryGetValue(runtimeTypeHandle, out typeDescriptor))
			{
				lock (typeDescriptorDictionaryLock)
				{
					if (typeDescriptorDictionary.TryGetValue(runtimeTypeHandle, out typeDescriptor))
					{
						typeDescriptorDictionary.Remove(runtimeTypeHandle);
					}
				}
			}
		}

    }
}